#include "StdAfx.h"
#include "Camera.h"

CCamera::CCamera()
{
    m_pPlayer = NULL;
    
	m_vRight = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
    m_vUp = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
    m_vLookAt = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
    m_vPosition = D3DXVECTOR3(0.0f, 0.0f, 0.0f);

    m_fFOV = 60.0f;
    m_fNearClip = 1.0f;
    m_fFarClip = 100.0f;

	m_vpViewport.X = 0;
    m_vpViewport.Y = 0;
    m_vpViewport.Width = 640;
    m_vpViewport.Height = 480;
    m_vpViewport.MinZ = 0.0f;
    m_vpViewport.MaxZ = 1.0f;

    m_bViewMatrixDirty = true;
    m_bProjectionMatrixDirty = true;
    m_bFrustumDirty = true;

    D3DXMatrixIdentity(&m_mtxView);
    D3DXMatrixIdentity(&m_mtxProjection);
}

CCamera::CCamera(const CCamera *pCamera)
{
    m_pPlayer = NULL;

	m_vRight = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
    m_vUp = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
    m_vLookAt = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
    m_vPosition = D3DXVECTOR3(0.0f, 0.0f, 0.0f);

    m_fFOV = 60.0f;
    m_fNearClip = 1.0f;
    m_fFarClip = 100.0f;

	m_vpViewport.X = 0;
    m_vpViewport.Y = 0;
    m_vpViewport.Width = 640;
    m_vpViewport.Height = 480;
    m_vpViewport.MinZ = 0.0f;
    m_vpViewport.MaxZ = 1.0f;

    m_bViewMatrixDirty = true;
    m_bProjectionMatrixDirty = true;
    m_bFrustumDirty = true;

    D3DXMatrixIdentity(&m_mtxView);
    D3DXMatrixIdentity(&m_mtxProjection);
}

CCamera::~CCamera()
{
}

void CCamera::AttachToPlayer(CPlayer *pPlayer)
{
    m_pPlayer = pPlayer;
}

void CCamera::DetachFromPlayer()
{
    m_pPlayer = NULL;
}

void CCamera::SetViewportToDevice(long nLeft, long nTop, long nWidth, long nHeight, float fNearClip, float fFarClip, LPDIRECT3DDEVICE9 pDevice)
{
    m_vpViewport.X = nLeft;
    m_vpViewport.Y = nTop;
    m_vpViewport.Width = nWidth;
    m_vpViewport.Height = nHeight;
    m_vpViewport.MinZ = 0.0f;
    m_vpViewport.MaxZ = 1.0f;

	m_fNearClip = fNearClip;
    m_fFarClip = fFarClip;
    
    m_bProjectionMatrixDirty = true;
    m_bFrustumDirty = true;

	if (pDevice) pDevice->SetViewport(&m_vpViewport);
}

const D3DXMATRIX& CCamera::GetProjectionMatrix()
{
    if (m_bProjectionMatrixDirty) 
    {     
        float fAspect = (float)m_vpViewport.Width / (float)m_vpViewport.Height;
        D3DXMatrixPerspectiveFovLH(&m_mtxProjection, D3DXToRadian(m_fFOV), fAspect, m_fNearClip, m_fFarClip);
        m_bProjectionMatrixDirty = false; 
    } 
    return(m_mtxProjection);
}

const D3DXMATRIX& CCamera::GetViewMatrix()
{
    if (m_bViewMatrixDirty) 
    {
        D3DXVec3Normalize(&m_vLookAt, &m_vLookAt);
        D3DXVec3Cross(&m_vRight, &m_vUp, &m_vLookAt);
        D3DXVec3Normalize(&m_vRight, &m_vRight);
        D3DXVec3Cross(&m_vUp, &m_vLookAt, &m_vRight);
        D3DXVec3Normalize(&m_vUp, &m_vUp);
        m_mtxView._11 = m_vRight.x; m_mtxView._12 = m_vUp.x; m_mtxView._13 = m_vLookAt.x;
	    m_mtxView._21 = m_vRight.y; m_mtxView._22 = m_vUp.y; m_mtxView._23 = m_vLookAt.y;
	    m_mtxView._31 = m_vRight.z; m_mtxView._32 = m_vUp.z; m_mtxView._33 = m_vLookAt.z;
	    m_mtxView._41 =- D3DXVec3Dot(&m_vPosition, &m_vRight);
	    m_mtxView._42 =- D3DXVec3Dot(&m_vPosition, &m_vUp);
	    m_mtxView._43 =- D3DXVec3Dot(&m_vPosition, &m_vLookAt);
        m_bViewMatrixDirty = false;
    } 
    return(m_mtxView);
}

void CCamera::SetViewMatrixToDevice(LPDIRECT3DDEVICE9 pD3DDevice)
{  
    if (pD3DDevice) pD3DDevice->SetTransform(D3DTS_VIEW, &GetViewMatrix());
}

void CCamera::SetProjectionMatrixToDevice(LPDIRECT3DDEVICE9 pD3DDevice)
{  
    if (pD3DDevice) pD3DDevice->SetTransform(D3DTS_PROJECTION, &GetProjectionMatrix());
}

void CCamera::CalculateFrustumPlanes()
{
    if (m_bFrustumDirty)
	{
		D3DXMATRIX m = GetViewMatrix() * GetProjectionMatrix();

		// Left clipping plane
		m_FrustumPlanes[0].a = -(m._14 + m._11);
		m_FrustumPlanes[0].b = -(m._24 + m._21);
		m_FrustumPlanes[0].c = -(m._34 + m._31);
		m_FrustumPlanes[0].d = -(m._44 + m._41);
		// Right clipping plane
		m_FrustumPlanes[1].a = -(m._14 - m._11);
		m_FrustumPlanes[1].b = -(m._24 - m._21);
		m_FrustumPlanes[1].c = -(m._34 - m._31);
		m_FrustumPlanes[1].d = -(m._44 - m._41);
		// Top clipping plane
		m_FrustumPlanes[2].a = -(m._14 - m._12);
		m_FrustumPlanes[2].b = -(m._24 - m._22);
		m_FrustumPlanes[2].c = -(m._34 - m._32);
		m_FrustumPlanes[2].d = -(m._44 - m._42);
		// Bottom clipping plane
		m_FrustumPlanes[3].a = -(m._14 + m._12);
		m_FrustumPlanes[3].b = -(m._24 + m._22);
		m_FrustumPlanes[3].c = -(m._34 + m._32);
		m_FrustumPlanes[3].d = -(m._44 + m._42);
		// Near clipping plane
		m_FrustumPlanes[4].a = -(m._13);
		m_FrustumPlanes[4].b = -(m._23);
		m_FrustumPlanes[4].c = -(m._33);
		m_FrustumPlanes[4].d = -(m._43);
		// Far clipping plane
		m_FrustumPlanes[5].a = -(m._14 - m._13);
		m_FrustumPlanes[5].b = -(m._24 - m._23);
		m_FrustumPlanes[5].c = -(m._34 - m._33);
		m_FrustumPlanes[5].d = -(m._44 - m._43);

		for (int i = 0; i < 6; i++) D3DXPlaneNormalize(&m_FrustumPlanes[i], &m_FrustumPlanes[i]);

		m_bFrustumDirty = false;
	}
}

bool CCamera::IsInFrustum(const D3DXVECTOR3& vMinBounding, const D3DXVECTOR3& vMaxBounding)
{
    D3DXVECTOR3 vNearPoint, vFarPoint, vNormal;

    CalculateFrustumPlanes();

    for (int i = 0; i < 6; i++)
    {
        vNormal = D3DXVECTOR3(m_FrustumPlanes[i].a, m_FrustumPlanes[i].b, m_FrustumPlanes[i].c);
        if (vNormal.x > 0.0f)
        {
            if (vNormal.y > 0.0f)
            {
                if (vNormal.z > 0.0f) 
                {
                    vNearPoint.x = vMinBounding.x; vNearPoint.y = vMinBounding.y; vNearPoint.z = vMinBounding.z;                
                } 
                else 
                {
                    vNearPoint.x = vMinBounding.x; vNearPoint.y = vMinBounding.y; vNearPoint.z = vMaxBounding.z;                 
                } 
            } 
            else
            {
                if (vNormal.z > 0.0f) 
                {
                    vNearPoint.x = vMinBounding.x; vNearPoint.y = vMaxBounding.y; vNearPoint.z = vMinBounding.z;
                } 
                else 
                {
                    vNearPoint.x = vMinBounding.x; vNearPoint.y = vMaxBounding.y; vNearPoint.z = vMaxBounding.z;                 
                } 
            } 
        } 
        else
        {
            if (vNormal.y > 0.0f)
            {
                if (vNormal.z > 0.0f) 
                {
                    vNearPoint.x = vMaxBounding.x; vNearPoint.y = vMinBounding.y; vNearPoint.z = vMinBounding.z;
                } 
                else 
                {
                    vNearPoint.x = vMaxBounding.x; vNearPoint.y = vMinBounding.y; vNearPoint.z = vMaxBounding.z;                 
                } 
            } 
            else
            {
                if (vNormal.z > 0.0f) 
                {
                    vNearPoint.x = vMaxBounding.x; vNearPoint.y = vMaxBounding.y; vNearPoint.z = vMinBounding.z;                
                } 
                else 
                {
                    vNearPoint.x = vMaxBounding.x; vNearPoint.y = vMaxBounding.y; vNearPoint.z = vMaxBounding.z;                 
                } 
            } 
        } 
        if ((D3DXVec3Dot(&vNormal, &vNearPoint) + m_FrustumPlanes[i].d) > 0.0f) return(false);
    } 
    return(true);
}

void CCamera::SetBoundingCube(const BOUNDINGCUBE& bcBoundingCube)
{
    m_bcBoundingCube = bcBoundingCube;
}

const BOUNDINGCUBE& CCamera::GetBoundingCube() const
{
    return(m_bcBoundingCube);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CFirstPersonCamera

CFirstPersonCamera::CFirstPersonCamera(const CCamera *pCamera)
{
    SetCameraDetails(pCamera);
}

void CFirstPersonCamera::SetCameraDetails(const CCamera *pCamera)
{
	if (pCamera) 
	{
		m_vPosition = pCamera->GetPosition();
		m_vRight = pCamera->GetRightVector();
		m_vLookAt = pCamera->GetLookAtVector();
		m_vUp = pCamera->GetUpVector();

		m_fFOV = pCamera->GetFOV();
		m_fNearClip = pCamera->GetNearClip();
		m_fFarClip = pCamera->GetFarClip();
		m_vpViewport = pCamera->GetViewport();
		m_bcBoundingCube = pCamera->GetBoundingCube();

		if (pCamera->GetCameraMode() == SPACESHIP_CAMERA)
		{
			m_vUp = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
			m_vRight.y = 0.0f;
			m_vLookAt.y = 0.0f;
			D3DXVec3Normalize(&m_vRight, &m_vRight);
			D3DXVec3Normalize(&m_vLookAt, &m_vLookAt);
		} 
		m_bViewMatrixDirty = true;
		m_bProjectionMatrixDirty = true;
		m_bFrustumDirty = true;
	}
}

void CFirstPersonCamera::Rotate(float x, float y, float z)
{
    D3DXMATRIX mtxRotate;
    if (x != 0.0f) 
    {
        D3DXMatrixRotationAxis(&mtxRotate, &m_vRight, D3DXToRadian(x));        
        D3DXVec3TransformNormal(&m_vLookAt, &m_vLookAt, &mtxRotate);
        D3DXVec3TransformNormal(&m_vUp, &m_vUp, &mtxRotate);
        D3DXVec3TransformNormal(&m_vRight, &m_vRight, &mtxRotate);
    } 
    if (m_pPlayer && (y != 0.0f))
    {
        D3DXMatrixRotationAxis(&mtxRotate, &m_pPlayer->GetUpVector(), D3DXToRadian(y));
        D3DXVec3TransformNormal(&m_vLookAt, &m_vLookAt, &mtxRotate);
        D3DXVec3TransformNormal(&m_vUp, &m_vUp, &mtxRotate);
        D3DXVec3TransformNormal(&m_vRight, &m_vRight, &mtxRotate);   
    } 
    if (m_pPlayer && (z != 0.0f))
    {
        D3DXMatrixRotationAxis(&mtxRotate, &m_pPlayer->GetLookAtVector(), D3DXToRadian(z));
        m_vPosition -= m_pPlayer->GetPosition();
        D3DXVec3TransformCoord (&m_vPosition, &m_vPosition, &mtxRotate);
        m_vPosition += m_pPlayer->GetPosition();
        D3DXVec3TransformNormal(&m_vLookAt, &m_vLookAt, &mtxRotate);
        D3DXVec3TransformNormal(&m_vUp, &m_vUp, &mtxRotate);
        D3DXVec3TransformNormal(&m_vRight, &m_vRight, &mtxRotate);    
    } 
    m_bViewMatrixDirty = true;
    m_bFrustumDirty = true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CThirdPersonCamera

CThirdPersonCamera::CThirdPersonCamera(const CCamera *pCamera)
{
    SetCameraDetails(pCamera);
}

void CThirdPersonCamera::SetCameraDetails(const CCamera *pCamera)
{
    if (pCamera) 
	{
		m_vPosition = pCamera->GetPosition();
		m_vRight = pCamera->GetRightVector();
		m_vLookAt = pCamera->GetLookAtVector();
		m_vUp = pCamera->GetUpVector();

		m_fFOV = pCamera->GetFOV();
		m_fNearClip = pCamera->GetNearClip();
		m_fFarClip = pCamera->GetFarClip();
		m_vpViewport = pCamera->GetViewport();
		m_bcBoundingCube = pCamera->GetBoundingCube();

		if (pCamera->GetCameraMode() == SPACESHIP_CAMERA)
		{
			m_vUp = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
			m_vRight.y = 0.0f;
			m_vLookAt.y = 0.0f;
			D3DXVec3Normalize(&m_vRight, &m_vRight);
			D3DXVec3Normalize(&m_vLookAt, &m_vLookAt);
		} 
		m_bViewMatrixDirty = true;
		m_bProjectionMatrixDirty = true;
		m_bFrustumDirty = true;
	}
}

void CThirdPersonCamera::Update(float fTimeScale, float fLag)
{
    if (m_pPlayer) 
	{
		D3DXMATRIX  mtxRotate;
		D3DXVECTOR3 vOffset, vPosition, vDirection;

		float fLagTimeScale = 1.0f, fLength = 0.0f;
		if (fLag != 0.0f) fLagTimeScale = fTimeScale * (1.0f / fLag);

		D3DXMatrixIdentity(&mtxRotate);
		D3DXVECTOR3 vRight = m_pPlayer->GetRightVector(), vUp = m_pPlayer->GetUpVector(), vLook = m_pPlayer->GetLookAtVector();
		mtxRotate._11 = vRight.x; mtxRotate._21 = vUp.x; mtxRotate._31 = vLook.x;
		mtxRotate._12 = vRight.y; mtxRotate._22 = vUp.y; mtxRotate._32 = vLook.y;
		mtxRotate._13 = vRight.z; mtxRotate._23 = vUp.z; mtxRotate._33 = vLook.z;

		D3DXVec3TransformCoord(&vOffset, &m_pPlayer->GetCameraOffset(), &mtxRotate);
		vPosition = m_pPlayer->GetPosition() + vOffset;
		vDirection = vPosition - m_vPosition;
		fLength = D3DXVec3Length(&vDirection);
		D3DXVec3Normalize(&vDirection, &vDirection);
		float fDistance = fLength * fLagTimeScale;
		if (fDistance > fLength) fDistance = fLength;
		if (fLength < 0.01f) fDistance = fLength;
		if (fDistance > 0)
		{
			m_vPosition += vDirection * fDistance;
			SetLookAt(m_pPlayer->GetPosition());
			m_bViewMatrixDirty = true;
			m_bFrustumDirty = true;
		} 
	}
}

void CThirdPersonCamera::SetLookAt(const D3DXVECTOR3& vLookAt)
{
    D3DXMATRIX m;
    D3DXMatrixLookAtLH(&m, &m_vPosition, &vLookAt, &m_pPlayer->GetUpVector());
    m_vRight = D3DXVECTOR3(m._11, m._21, m._31);
    m_vUp = D3DXVECTOR3(m._12, m._22, m._32);
    m_vLookAt = D3DXVECTOR3(m._13, m._23, m._33);
    m_bViewMatrixDirty = true;
    m_bFrustumDirty = true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CSpaceShipCamera

CSpaceShipCamera::CSpaceShipCamera(const CCamera *pCamera)
{
    SetCameraDetails(pCamera);
}

void CSpaceShipCamera::SetCameraDetails(const CCamera *pCamera)
{
    if (pCamera) 
	{
		m_vPosition = pCamera->GetPosition();
		m_vRight = pCamera->GetRightVector();
		m_vLookAt = pCamera->GetLookAtVector();
		m_vUp = pCamera->GetUpVector();

		m_fFOV = pCamera->GetFOV();
		m_fNearClip = pCamera->GetNearClip();
		m_fFarClip = pCamera->GetFarClip();
		m_vpViewport = pCamera->GetViewport();
		m_bcBoundingCube = pCamera->GetBoundingCube();

		m_bViewMatrixDirty = true;
		m_bProjectionMatrixDirty = true;
		m_bFrustumDirty = true;
	}
}

void CSpaceShipCamera::Rotate(float x, float y, float z)
{
    D3DXMATRIX mtxRotate;
    if (m_pPlayer && (x != 0.0f))
    {
        D3DXMatrixRotationAxis(&mtxRotate, &m_pPlayer->GetRightVector(), D3DXToRadian(x));
        D3DXVec3TransformNormal(&m_vLookAt, &m_vLookAt, &mtxRotate);
        D3DXVec3TransformNormal(&m_vUp, &m_vUp, &mtxRotate);
        m_vPosition -= m_pPlayer->GetPosition();
        D3DXVec3TransformCoord(&m_vPosition, &m_vPosition, &mtxRotate);
        m_vPosition += m_pPlayer->GetPosition();
    } 
    if (m_pPlayer && (y != 0.0f))
    {
        D3DXMatrixRotationAxis(&mtxRotate, &m_pPlayer->GetUpVector(), D3DXToRadian(y));
        D3DXVec3TransformNormal(&m_vLookAt, &m_vLookAt, &mtxRotate);
        D3DXVec3TransformNormal(&m_vRight, &m_vRight, &mtxRotate);
        m_vPosition -= m_pPlayer->GetPosition();
        D3DXVec3TransformCoord(&m_vPosition, &m_vPosition, &mtxRotate);
        m_vPosition += m_pPlayer->GetPosition();   
    } 
    if (m_pPlayer && (z != 0.0f))
    {
        D3DXMatrixRotationAxis(&mtxRotate, &m_pPlayer->GetLookAtVector(), D3DXToRadian(z));
        D3DXVec3TransformNormal(&m_vUp, &m_vUp, &mtxRotate);
        D3DXVec3TransformNormal(&m_vRight, &m_vRight, &mtxRotate);
        m_vPosition -= m_pPlayer->GetPosition();
        D3DXVec3TransformCoord(&m_vPosition, &m_vPosition, &mtxRotate);
        m_vPosition += m_pPlayer->GetPosition();
    } 
    m_bViewMatrixDirty = true;
    m_bFrustumDirty = true;
}
